/**
 * 版权所有(C)，上海勾芒信息科技，2017，所有权利保留。
 * 
 * 项目名：	cards-web
 * 文件名：	WxMessageRouter.java
 * 模块说明：	
 * 修改历史：
 * 2017年5月31日 - Debenson - 创建。
 */
package me.chanjar.weixin.message.rule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alex.wechat.core.handler.WxAccountType;

import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker;
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.InternalSession;
import me.chanjar.weixin.common.session.InternalSessionManager;
import me.chanjar.weixin.common.session.StandardSessionManager;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.LogExceptionHandler;
import me.chanjar.weixin.message.bean.WxXmlMessage;

/**
 * @author Debenson
 * @since 0.1
 */
public class WxMessageRouter {
  private static final Logger logger = LoggerFactory.getLogger(WxMessageRouter.class);

  private static final int DEFAULT_THREAD_POOL_SIZE = 100;
  private final List<WxMessageRouterRule> mpRules = new ArrayList<>();
  private final List<WxMessageRouterRule> appRules = new ArrayList<>();
  private final List<WxMessageRouterRule> compRules = new ArrayList<>();
  private ExecutorService executorService;
  private WxMessageDuplicateChecker messageDuplicateChecker;
  private WxSessionManager sessionManager;
  private WxErrorExceptionHandler exceptionHandler;

  public WxMessageRouter() {
    this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
    this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
    this.sessionManager = new StandardSessionManager();
    this.exceptionHandler = new LogExceptionHandler();
  }

  /**
   * <pre>
   * 设置自定义的 {@link ExecutorService}
   * 如果不调用该方法，默认使用 Executors.newFixedThreadPool(100)
   * </pre>
   * 
   * @param executorService
   */
  public void setExecutorService(ExecutorService executorService) {
    this.executorService = executorService;
  }

  /**
   * <pre>
   * 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker}
   * 如果不调用该方法，默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker}
   * </pre>
   * 
   * @param messageDuplicateChecker
   */
  public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) {
    this.messageDuplicateChecker = messageDuplicateChecker;
  }

  /**
   * <pre>
   * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager}
   * 如果不调用该方法，默认使用 {@link me.chanjar.weixin.common.session.StandardSessionManager}
   * </pre>
   * 
   * @param sessionManager
   */
  public void setSessionManager(WxSessionManager sessionManager) {
    this.sessionManager = sessionManager;
  }

  /**
   * <pre>
   * 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler}
   * 如果不调用该方法，默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler}
   * </pre>
   * 
   * @param exceptionHandler
   */
  public void setExceptionHandler(WxErrorExceptionHandler exceptionHandler) {
    this.exceptionHandler = exceptionHandler;
  }

  /**
   * 开始一个新的Route规则
   */
  public WxMessageRouterRule rule() {
    return new WxMessageRouterRule(this);
  }

  void addRule(WxMessageRouterRule rule) {
    if (rule == null) {
      throw new IllegalArgumentException("argument rule cannot be null");
    }

    List<WxMessageRouterRule> destRules = null;
    if (WxAccountType.MP.equals(rule.getAccountType())) {
      destRules = mpRules;
    } else if (WxAccountType.APP.equals(rule.getAccountType())) {
      destRules = appRules;
    } else if (WxAccountType.COMPONENT.equals(rule.getAccountType())) {
      destRules = compRules;
    } else {
      throw new IllegalArgumentException("illegal account type of rule");
    }

    // 添加并按优先级排序
    destRules.add(rule);
    sortRules(destRules);
  }

  /**
   * 按规则的优先级从小到大排序规则
   * 
   * @param rules
   */
  private static void sortRules(List<WxMessageRouterRule> rules) {
    if (rules != null) {
      Collections.sort(rules, new Comparator<WxMessageRouterRule>() {

        @Override
        public int compare(WxMessageRouterRule o1, WxMessageRouterRule o2) {
          return Integer.compare(o2.getPriority(), o1.getPriority());
        }

      });
    }
  }

  /**
   * 处理微信消息
   * 
   * @param wxMessage
   * @return 处理结果
   * @deprecated 请使用 {@link #routeMp(WxXmlMessage)}。
   */
  public String route(final WxXmlMessage wxMessage) {
    return doRoute(wxMessage, this.mpRules);
  }

  /**
   * 处理公众号微信消息
   * 
   * @param wxMessage
   * @return 处理结果
   */
  public String routeMp(final WxXmlMessage wxMessage) {
    return doRoute(wxMessage, this.mpRules);
  }

  /**
   * 处理小程序微信消息
   * 
   * @param wxMessage
   * @return 处理结果
   */
  public String routeApp(final WxXmlMessage wxMessage) {
    return doRoute(wxMessage, this.appRules);
  }

  /**
   * 处理第三方开放平台微信消息
   * 
   * @param wxMessage
   * @return 处理结果
   */
  public String routeComponent(final WxXmlMessage wxMessage) {
    return doRoute(wxMessage, this.compRules);
  }

  protected String doRoute(final WxXmlMessage wxMessage, List<WxMessageRouterRule> allRules) {
    if (isDuplicateMessage(wxMessage)) {
      // 如果是重复消息，那么就不做处理
      if (logger.isInfoEnabled()) {
        logger.info("判定为重复消息，不回复");
      }
      return null;
    }

    final List<WxMessageRouterRule> matchRules = new ArrayList<>();
    // 收集匹配的规则
    for (final WxMessageRouterRule rule : allRules) {
      if (rule.test(wxMessage)) {
        matchRules.add(rule);
        if (!rule.isReEnter()) {
          break;
        }
      }
    }

    if (matchRules.size() == 0) {
      return null;
    }

    String res = null;
    final List<Future<?>> futures = new ArrayList<>();
    for (final WxMessageRouterRule rule : matchRules) {
      // 返回最后一个非异步的rule的执行结果
      if (rule.isAsync()) {
        futures.add(this.executorService.submit(new Runnable() {
          @Override
          public void run() {
            try {
              rule.service(wxMessage);
            } catch (WxErrorException e) {
              exceptionHandler.handle(e);
            }
          }
        }));
      } else {
        try {
          res = rule.service(wxMessage);
        } catch (WxErrorException e) {
          exceptionHandler.handle(e);
        } finally {
          // 在同步操作结束，session访问结束
          logger.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
          sessionEndAccess(wxMessage);
        }
      }
    }

    if (futures.size() > 0) {
      this.executorService.submit(new Runnable() {
        @Override
        public void run() {
          for (Future<?> future : futures) {
            try {
              future.get();
              logger.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser());
              // 异步操作结束，session访问结束
              sessionEndAccess(wxMessage);
            } catch (InterruptedException e) {
              logger.error("Error happened when wait task finish", e);
            } catch (ExecutionException e) {
              logger.error("Error happened when wait task finish", e);
            }
          }
        }
      });
    }
    return res;
  }

  protected boolean isDuplicateMessage(WxXmlMessage wxMessage) {
    StringBuffer messageId = new StringBuffer();
    if (wxMessage.getMsgId() == null) {
      messageId.append(wxMessage.getCreateTime()).append("-").append(wxMessage.getFromUser())
          .append("-").append(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey())
          .append("-").append(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()).append("-")
          .append(wxMessage.getInfoType() == null ? "" : wxMessage.getInfoType());
    } else {
      messageId.append(wxMessage.getMsgId());
    }

    return this.messageDuplicateChecker.isDuplicate(messageId.toString());

  }

  /**
   * 对session的访问结束
   * 
   * @param wxMessage
   */
  protected void sessionEndAccess(WxXmlMessage wxMessage) {

    InternalSession session = ((InternalSessionManager) this.sessionManager)
        .findSession(wxMessage.getFromUser());
    if (session != null) {
      session.endAccess();
    }

  }

}
